package com.xyy.saas.payment.payment.core.service;

import com.alibaba.dubbo.config.annotation.Reference;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.xyy.ec.order.business.api.OrderPayBusinessApi;
import com.xyy.ec.order.business.api.OrderRefundBusinessApi;
import com.xyy.ec.order.business.dto.OrderRefundBusinessDto;
import com.xyy.ec.order.business.dto.pay.OrderPayBusinessDto;
import com.xyy.framework.redis.core.RedisClient;
import com.xyy.saas.payment.adpater.RouteManager;
import com.xyy.saas.payment.adpater.fumin.pojo.domain.SeparateAccountDO;
import com.xyy.saas.payment.adpater.impl.JdCreditAdapter;
import com.xyy.saas.payment.adpater.jdCredit.resp.LoanInfoResponse;
import com.xyy.saas.payment.adpater.pingxx.domain.RefundDO;
import com.xyy.saas.payment.callback.core.result.CallbackRunner;
import com.xyy.saas.payment.common.Constants;
import com.xyy.saas.payment.common.RocketMqTopic;
import com.xyy.saas.payment.common.pool.NamedTask;
import com.xyy.saas.payment.common.constants.ChannelConstants;
import com.xyy.saas.payment.common.constants.MqConstant;
import com.xyy.saas.payment.common.exception.PaymentException;
import com.xyy.saas.payment.common.model.Tuple2;
import com.xyy.saas.payment.common.pool.PoolUtil;
import com.xyy.saas.payment.config.BusinessTypeConfig;
import com.xyy.saas.payment.converter.PayCommonConverter;
import com.xyy.saas.payment.cores.bo.*;
import com.xyy.saas.payment.cores.enums.*;
import com.xyy.saas.payment.cores.param.NotifyOfflineRefundParam;
import com.xyy.saas.payment.cores.vo.*;
import com.xyy.saas.payment.dao.mapper.*;
import com.xyy.saas.payment.dao.model.*;
import com.xyy.saas.payment.merchant.core.dto.*;
import com.xyy.saas.payment.merchant.core.service.UserInfoService2;
import com.xyy.saas.payment.merchant.core.vo.Recipient;
import com.xyy.saas.payment.merchant.core.vo.SettleAccountDto;
import com.xyy.saas.payment.merchant.core.vo.UserDto;
import com.xyy.saas.payment.merchant.core.vo.UserInfoVo;
import com.xyy.saas.payment.payment.core.bo.RefundBO;
import com.xyy.saas.payment.payment.core.bo.WithdrawalBO;
import com.xyy.saas.payment.payment.core.conversion.ConversionDomain;
import com.xyy.saas.payment.payment.core.conversion.ConversionVO;
import com.xyy.saas.payment.payment.core.dto.*;
import com.xyy.saas.payment.service.*;
import com.xyy.saas.payment.settle.core.service.AccountService;
import com.xyy.saas.payment.trace.AlarmService;
import com.xyy.saas.payment.trace.DingTalkMessage;
import com.xyy.saas.payment.util.*;
import com.xyy.saas.payment.util.mq.MqProducer;
import com.xyy.util.StringUtil;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Service;
import util.AESUtil;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;


/**
 * @author chenshuyi
 * @date 2019/10/22 11:07
 */
@Service
@DependsOn("businessTypeConfig")
public class PaymentService {

    private static Logger LOGGER = LoggerFactory.getLogger(PaymentService.class);
    @Value("${refundAlarmSwitch:true}")
    private Boolean refundAlarmSwitch;

    @Value("${refundMaxRetryCount:3}")
    private Integer refundMaxRetryCount;

    @Value("${refundLockExpireTime:10}")
    private Integer refundLockExpireTime;

    @Value("${noDelaySwitch:false}")
    private Boolean noDelaySwitch;

    @Value("${noDelayShopSwitch:false}")
    private Boolean noDelayShopSwitch;
    /**
     * 平安渠道退款不延迟店铺
     */
    @Value("#{'${noDelayPopShop}'.split(',')}")
    private List<String> POP_SHOP;
    @Autowired
    private AlarmService alarmService;
    @Resource
    private RouteManager routeManager;

    @Autowired
    private PayAccountService payAccountService;

    @Autowired
    private PrePayOrderService prePayOrderService;

    @Autowired
    private PayResultOrderService payResultOrderService;

    @Autowired
    private WithdrawOrderMapper withdrawOrderMapper;

    @Autowired
    private ThirdUserInfoMapper thirdUserInfoMapper;

    @Autowired
    private MqProducer mqProducer;

    @Autowired
    private LeafUtil leafUtil;

    @Autowired
    private RefundOrderMapper refundOrderMapper;

    @Autowired
    private RedisClient redisClient;

    @Autowired
    private PrePayOrderMapper prePayOrderMapper;

    @Autowired
    private PayResultOrderMapper payResultOrderMapper;

    @Autowired
    private BusinessTypeConfig businessTypeConfig;

    @Autowired
    private OrderService orderService;

    @Autowired
    private ThirdUserInfoService thirdUserInfoService;

    @Autowired
    private AccountService accountService;

    @Autowired
    private SeparateOrderService separateOrderService;

    @Resource
    private RedisUtil redisUtil;

    @Autowired
    private FrozenSettlementService frozenSettlementService;
    @Autowired
    private UserInfoService userInfoService;
    @Resource
    private CallbackRunner callbackRunner;
    @Resource
    private UserInfoService2 userInfoService2;
    @Reference(version = "1.0.0")
    private OrderPayBusinessApi orderPayBusinessApi;
    @Reference(version = "1.0.0")
    private OrderRefundBusinessApi orderRefundBusinessApi;
    @Resource
    private JdCreditAdapter jdCreditAdapter;

    private static Map<String, String> channelModeMap = new HashMap<>();

    static {
        channelModeMap.put(ChannelEnum.ALIPAY.getCode()+""+ PayModeEnum.MINI.getMode(), "alimini");
        channelModeMap.put(ChannelEnum.WX.getCode()+""+ PayModeEnum.APP.getMode(),"wxapp");
        channelModeMap.put(ChannelEnum.WX.getCode()+""+ PayModeEnum.MINI.getMode(),"wxmini");
    }

    public ResultVO<NotifyOfflineRefundBo> notifyOfflineRefund(NotifyOfflineRefundParam notifyOfflineRefundParam) {
        LOGGER.info("PaymentService#notifyOfflineRefund param:{}", JSON.toJSONString(notifyOfflineRefundParam));
        String refundNo = notifyOfflineRefundParam.getRefundNo();
        String lockKey = RedisUtil.NOTIFY_OFFLINE_REFUND_PREFIX + refundNo;
        try {
            if (!redisUtil.tryLock(lockKey)) {
                LOGGER.error("PaymentService#notifyOfflineRefund {} repeat_request", refundNo);
                return ResultVO.createError(ResultCodeEnum.REPEAT_REQUEST);
            }

            RefundOrder refundOrder = refundOrderMapper.selectByRefundNo(refundNo);
            if (refundOrder == null) {
                return ResultVO.createError(ResultCodeEnum.TRADE_NOT_EXIT_ERROR);
            }

            // 渠道校验
            // 暂时只处理农行，后续可扩展
            if (!Objects.equals(ChannelEnum.ABCHINA_LOAN.getCode(), refundOrder.getChannel())) {
                return ResultVO.createSuccess(new NotifyOfflineRefundBo().setRefundNo(refundNo));
            }

            // 退款状态校验
            if (Objects.equals(RefundOrder.STATUS_FINISH, refundOrder.getStatus())) {
                return ResultVO.createSuccess(new NotifyOfflineRefundBo().setRefundNo(refundNo));
            }

            // 金额校验
            if (!Objects.equals(refundOrder.getAmount(), notifyOfflineRefundParam.getAmount())) {
                return ResultVO.createError(ResultCodeEnum.PARAM_AMOUNT_ERROR);
            }

            // 通知渠道
            RefundDto refundDto = new RefundDto();
            fillRefundDtoByRefundOrder(refundDto, refundOrder, notifyOfflineRefundParam);
            RefundBO refundBO = routeManager.notifyRefund(refundDto);

            // 更新退款状态，需新加状态值区分现有的退款成功，预留

            return ResultVO.createSuccess(new NotifyOfflineRefundBo().setRefundNo(refundNo));
        } catch (PaymentException e) {
            LOGGER.error("PaymentService#notifyOfflineRefund pe error, param:{}", JSON.toJSONString(notifyOfflineRefundParam), e);
            return ResultVO.createError(e.getErrCode(), e.getErrMsg());
        } finally {
            redisUtil.unLock(lockKey);
        }
    }

    private void fillRefundDtoByRefundOrder(RefundDto refundDto, RefundOrder refundOrder, NotifyOfflineRefundParam notifyOfflineRefundParam) {
        refundDto.setPayNo(refundOrder.getPayNo());
        refundDto.setRefundNo(refundOrder.getRefundNo());
        refundDto.setAmount(refundOrder.getAmount());
        refundDto.setBusinessRefundNo(refundOrder.getBusinessRefundNo());
        refundDto.setChannel(refundOrder.getChannel());
        refundDto.setRefundType(notifyOfflineRefundParam.getRefundType());
    }

    public ResultVO<PrepayBo> prepay(PrepayDto prePayDto) throws PaymentException {
        LOGGER.info("PaymentService#prepay prePayDto:{}", JSON.toJSONString(prePayDto));
        PrepayBo prepayBo = new PrepayBo();
        //如果已经存在prePayOrder 则直接返回
        List<PrePayOrder> prePayList = prePayOrderService.getPrePayOrderByBusinessOrderNo(prePayDto.getBusinessOrderNo());

        if(prePayList != null && prePayList.size() > 0) {
            LOGGER.warn("PaymentService prepay prePayOrder is exist prePayDto:{}", JSON.toJSON(prePayDto));
            // 本次取出第一个创建的订单
            PrePayOrder prePayOrder = prePayList.get(0);
            prepayBo.setPayNo(prePayOrder.getPayNo());
            prepayBo.setUserId(prePayOrder.getReceiverId());
            return ResultVO.createSuccess(ConversionVO.buildPrepayVO(prepayBo));
        }

        String userId = payAccountService.getAccountIdByBusiness(prePayDto.getBusinessIdType(), prePayDto.getBusinessId());

        if(userId == null) {
            LOGGER.warn("PaymentService prepay userId not found businessType:{}, businessId:{}", prePayDto.getBusinessType(), prePayDto.getBusinessId());
            throw new PaymentException(ResultCodeEnum.USER_NOT_FOUND);
        }

        prePayDto.setUserId(userId);
        //获取过期时间
        Object expire = JSONUtils.getValueFromJson(prePayDto.getExtra(), "timeExpire");
        String timeExpire = expire == null ? null: expire.toString();
        Date timeExpireDate;

        try {
            timeExpireDate = timeExpire== null ? DateUtil.getTomorrow() : DateUtil.parase(timeExpire, "yyyyMMddHHmmss");
        }
        catch(Exception e) {
            LOGGER.error("PingxxAdapter prepay praseDate error prepayDto:{}", JSON.toJSON(prePayDto));
            throw new PaymentException(ResultCodeEnum.SYSTEM_BUSY_ERROR);
        }

        prePayDto.setTimeExpire(timeExpireDate);
        String channel;
        if (Constants.thirdChannelSubmitPingan(prePayDto.getChannel())) {
            channel = Constants.transThirdCahnnelSubmitPingan(prePayDto.getChannel());
        } else {
            if (prePayDto.getChannel() != null) {
                if (prePayDto.getChannel().contains(ChannelEnum.ALIPAY.getCode()) && BusinessOrderTypeEnum.EC_POP.getType().equals(prePayDto.getBusinessOrderType())) {
                    channel=prePayDto.getChannel();
                } else if (prePayDto.getChannel().contains(ChannelEnum.JD_CREDIT.getCode())) {
                    channel=prePayDto.getChannel();
                } else if (Objects.equals(prePayDto.getChannel(), ChannelEnum.ABCHINA_LOAN.getCode())) {
                    channel = prePayDto.getChannel();
                } else if (Objects.equals(prePayDto.getChannel(), ChannelEnum.XYD_LOAN.getCode())) {
                    channel = prePayDto.getChannel();
                } else {
                    channel = prePayDto.getChannel().startsWith(Constants.CHANNEL_AGGREGATE_NAME) ? Constants.CHANNEL_AGGREGATE_NAME : Constants.CHANNEL_WX_NAME;
                }
            } else {
                channel = Constants.CHANNEL_AGGREGATE_NAME;
            }
        }

        prepayBo = routeManager.prepay(prePayDto, channel);
        LOGGER.info("PaymentService#record prepayBO:{}", JSON.toJSONString(prepayBo));
        return ResultVO.createSuccess(ConversionVO.buildPrepayVO(prepayBo));
    }

    public void checkOrderPrepayVo(OrderPrepayVo orderPrepayVo) throws PaymentException {
        if (orderPrepayVo == null) {
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR);
        }
        if (StringUtils.isEmpty(orderPrepayVo.getCallback())) {
            throw new PaymentException(ResultCodeEnum.CALLBACK_URL_UN_LEGAL);
        }
        if (StringUtil.isEmpty(orderPrepayVo.getBusinessOrderNo(), orderPrepayVo.getPayeeId(), orderPrepayVo.getInvoker().getType(),
                orderPrepayVo.getBusinessOrderType().getType(),  orderPrepayVo.getBusinessType().getType())|| Objects.isNull(orderPrepayVo.getAmount()) ) {
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR);
        }

        if (orderPrepayVo.getBusinessOrderNo().length() > Constants.MAX_STRING_LENGTH_NO ||
                orderPrepayVo.getPayeeId().length() > Constants.MAX_STRING_LENGTH_PRODUCT_NAME ||
                orderPrepayVo.getAmount() < Constants.MIN_AMOUNT ) {
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR);
        }
    }
    public void checkOrderPrepayVo(OrderPrepayVoV1 orderPrepayVo) throws PaymentException {
        if (orderPrepayVo == null) {
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR);
        }
        if (StringUtils.isEmpty(orderPrepayVo.getCallback())) {
            throw new PaymentException(ResultCodeEnum.CALLBACK_URL_UN_LEGAL);
        }
        if (StringUtil.isEmpty(orderPrepayVo.getBusinessOrderNo(), orderPrepayVo.getPayeeId(), orderPrepayVo.getInvoker(),
                orderPrepayVo.getBusinessOrderType(),  orderPrepayVo.getBusinessType())|| Objects.isNull(orderPrepayVo.getAmount()) ) {
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR);
        }

        if (orderPrepayVo.getBusinessOrderNo().length() > Constants.MAX_STRING_LENGTH_NO ||
                orderPrepayVo.getPayeeId().length() > Constants.MAX_STRING_LENGTH_PRODUCT_NAME ||
                orderPrepayVo.getAmount() < Constants.MIN_AMOUNT ) {
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR);
        }
        if(!businessTypeConfig.checkAllType(orderPrepayVo.getBusinessType(), orderPrepayVo.getBusinessIdType(), orderPrepayVo.getBusinessOrderType())) {
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR);
        }
    }

    public void checkPayVo(PayVo payVo) throws PaymentException {
        if (null == payVo) {
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR);
        }
        if (payVo.getInvoker()==null||payVo.getBusinessOrderType()==null||payVo.getBusinessType()==null||payVo.getBusinessIdType()==null){
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR);
        }
        if (StringUtils.isEmpty(payVo.getCallback())) {
            throw new PaymentException(ResultCodeEnum.CALLBACK_URL_UN_LEGAL);
        }
        if (StringUtil.isEmpty(payVo.getBusinessOrderNo(), payVo.getPayeeId(), payVo.getInvoker().getType(),
                payVo.getBusinessOrderType().getType(),  payVo.getBusinessType().getType())|| Objects.isNull(payVo.getAmount()) ) {
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR);
        }
        if (payVo.getBusinessOrderNo().length() > Constants.MAX_STRING_LENGTH_NO ||
                payVo.getPayeeId().length() > Constants.MAX_STRING_LENGTH_PRODUCT_NAME ||
                payVo.getAmount() < Constants.MIN_AMOUNT ) {
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR);
        }
        if (payVo.getChannel()==null||StringUtil.isEmpty(payVo.getBody())||payVo.getPayMode()==null||payVo.getBody().length()>127){
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR);
        }
    }

    public void checkPay4noCashier(Pay4noCashierVo pay4noCashier) throws PaymentException {
        if (null == pay4noCashier) {
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR);
        }
        if (pay4noCashier.getInvoker()==null||pay4noCashier.getBusinessOrderType()==null||pay4noCashier.getBusinessType()==null||pay4noCashier.getBusinessIdType()==null||StringUtils.isEmpty(pay4noCashier.getSubject())){
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR);
        }
        if (StringUtils.isEmpty(pay4noCashier.getCallback())) {
            throw new PaymentException(ResultCodeEnum.CALLBACK_URL_UN_LEGAL);
        }
        if (StringUtil.isEmpty(pay4noCashier.getBusinessOrderNo(), pay4noCashier.getPayeeId(), pay4noCashier.getInvoker().getType(),
                pay4noCashier.getBusinessOrderType().getType(),  pay4noCashier.getBusinessType().getType())|| Objects.isNull(pay4noCashier.getAmount()) ) {
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR);
        }
        if (pay4noCashier.getBusinessOrderNo().length() > Constants.MAX_STRING_LENGTH_NO ||
                pay4noCashier.getPayeeId().length() > Constants.MAX_STRING_LENGTH_PRODUCT_NAME ||
                pay4noCashier.getAmount() < Constants.MIN_AMOUNT ) {
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR);
        }
        if (pay4noCashier.getChannel()==null||StringUtil.isEmpty(pay4noCashier.getBody())||pay4noCashier.getPayMode()==null||pay4noCashier.getBody().length()>127){
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR);
        }
    }

    public void checkPay4noCashier(Pay4noCashierVoV1 pay4noCashier) throws PaymentException {
        if (null == pay4noCashier) {
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR);
        }
        if (pay4noCashier.getInvoker()==null||pay4noCashier.getBusinessOrderType()==null||pay4noCashier.getBusinessType()==null||pay4noCashier.getBusinessIdType()==null||StringUtils.isEmpty(pay4noCashier.getSubject())){
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR);
        }
        if (StringUtils.isEmpty(pay4noCashier.getCallback())) {
            throw new PaymentException(ResultCodeEnum.CALLBACK_URL_UN_LEGAL);
        }
        if (StringUtil.isEmpty(pay4noCashier.getBusinessOrderNo(), pay4noCashier.getPayeeId(), pay4noCashier.getInvoker(),
                pay4noCashier.getBusinessOrderType(),  pay4noCashier.getBusinessType())|| Objects.isNull(pay4noCashier.getAmount()) ) {
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR);
        }
        if (pay4noCashier.getBusinessOrderNo().length() > Constants.MAX_STRING_LENGTH_NO ||
                pay4noCashier.getPayeeId().length() > Constants.MAX_STRING_LENGTH_DESC ||
                pay4noCashier.getAmount() < Constants.MIN_AMOUNT ) {
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR);
        }
        if (pay4noCashier.getChannel()==null||StringUtil.isEmpty(pay4noCashier.getBody())||pay4noCashier.getPayMode()==null||pay4noCashier.getBody().length()>127){
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR);
        }
        if(!businessTypeConfig.checkAllType(pay4noCashier.getBusinessType(), pay4noCashier.getBusinessIdType(),pay4noCashier.getBusinessOrderType())) {
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR);
        }
        if (ChannelEnum.PINGAN_CREDIT.equals(pay4noCashier.getChannel()) && PayModeEnum.APP.equals(pay4noCashier.getPayMode())) {
            if (pay4noCashier.getMobileTerminalTypeEnum() == null) {
                throw new PaymentException(ResultCodeEnum.PARAM_ERROR);
            }
            if (pay4noCashier.getMobileTerminalTypeEnum() == MobileTerminalTypeEnum.ANDROID) {
                if (pay4noCashier.getMapJson() == null || !pay4noCashier.getMapJson().contains("androidCallBackAddress")) {
                    throw new PaymentException(ResultCodeEnum.PARAM_ERROR);
                }
            }
            if (pay4noCashier.getMobileTerminalTypeEnum() == MobileTerminalTypeEnum.iOS) {
                if (pay4noCashier.getMapJson() == null || !pay4noCashier.getMapJson().contains("iosCallBackAddress")) {
                    throw new PaymentException(ResultCodeEnum.PARAM_ERROR);
                }
            }
        }
        if (ChannelEnum.PINGAN_CREDIT.equals(pay4noCashier.getChannel()) && PayModeEnum.PC.equals(pay4noCashier.getPayMode())) {
            if (pay4noCashier.getMapJson() == null || !pay4noCashier.getMapJson().contains("frontSkipUrl")) {
                throw new PaymentException(ResultCodeEnum.PARAM_ERROR);
            }
        }
        if (Constants.CHANNEL_JD_CARD_NAME.equals(pay4noCashier.getChannel().getCode())) {
            if (pay4noCashier.getMapJson() == null) {
                throw new PaymentException(ResultCodeEnum.PARAM_ERROR);
            }
            JSONObject params = JSONObject.parseObject(pay4noCashier.getMapJson());
            if (!params.containsKey("merchantNo") || !params.containsKey("contractNo") || !params.containsKey("bankCardInfo")) {
                throw new PaymentException(ResultCodeEnum.PARAM_ERROR);
            }
        }
    }
    public void checkPrepayJumpCashier(PrepayJumpCashierVo prepayJumpCashierVo) throws PaymentException {
        if (null == prepayJumpCashierVo) {
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR);
        }
        if (prepayJumpCashierVo.getInvoker()==null||prepayJumpCashierVo.getBusinessOrderType()==null||prepayJumpCashierVo.getBusinessType()==null||prepayJumpCashierVo.getBusinessIdType()==null||StringUtils.isEmpty(prepayJumpCashierVo.getSubject())){
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR);
        }
        if (StringUtils.isEmpty(prepayJumpCashierVo.getCallback())) {
            throw new PaymentException(ResultCodeEnum.CALLBACK_URL_UN_LEGAL);
        }
        if (StringUtil.isEmpty(prepayJumpCashierVo.getBusinessOrderNo(), prepayJumpCashierVo.getPayeeId(), prepayJumpCashierVo.getInvoker(),
                prepayJumpCashierVo.getBusinessOrderType(),  prepayJumpCashierVo.getBusinessType())|| Objects.isNull(prepayJumpCashierVo.getAmount()) ) {
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR);
        }
        if (prepayJumpCashierVo.getBusinessOrderNo().length() > Constants.MAX_STRING_LENGTH_NO ||
                prepayJumpCashierVo.getPayeeId().length() > Constants.MAX_STRING_LENGTH_40 ||
                prepayJumpCashierVo.getAmount() < Constants.MIN_AMOUNT ) {
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR);
        }
        if (StringUtil.isEmpty(prepayJumpCashierVo.getBody())||prepayJumpCashierVo.getCashierEnum()==null||prepayJumpCashierVo.getBody().length()>127){
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR);
        }
        if(!businessTypeConfig.checkAllType(prepayJumpCashierVo.getBusinessType(), prepayJumpCashierVo.getBusinessIdType(),prepayJumpCashierVo.getBusinessOrderType())) {
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR);
        }

    }


    public void checkOrderPay(OrderPayVo orderPayVo) throws PaymentException {
        if (null == orderPayVo) {
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR);
        }
        if (orderPayVo.getInvoker()==null||orderPayVo.getBusinessOrderType()==null||orderPayVo.getBusinessType()==null||orderPayVo.getBusinessIdType()==null){
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR);
        }
    }

    public void checkOrderPay(OrderPayVoV1 orderPayVo) throws PaymentException {
        if (null == orderPayVo) {
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR);
        }
        if (orderPayVo.getInvoker()==null||orderPayVo.getBusinessOrderType()==null||orderPayVo.getBusinessType()==null||orderPayVo.getBusinessIdType()==null){
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR);
        }
    }

    /**
     * 创建退款单
     * @param refundDto
     * @return
     * @throws PaymentException
     */
    public RefundBo refund(RefundDto refundDto) throws PaymentException {
        LOGGER.info("PaymentService#refund refundDto:{}", JSON.toJSONString(refundDto));
        PayResultOrder payResultOrder = payResultOrderService.selectByPayNoAndChannel(refundDto.getPayNo(), refundDto.getChannel());

        if(payResultOrder == null) {
            throw new PaymentException(ResultCodeEnum.TRADE_NOT_EXIT_ERROR);
        }

        refundDto.setMerchantId(payResultOrder.getMerchantId());
        String refundNo = createRefundOrder(payResultOrder, refundDto);
        return ConversionVO.buildRefundBo(refundNo);
    }



    /**
     * 退款参数校验
     * @param orderRefundVo
     * @throws PaymentException
     */
    public void checkOrderRefundVo(OrderRefundVo orderRefundVo) throws PaymentException {
        if (orderRefundVo == null) {
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR);
        }
        if (StringUtils.isEmpty(orderRefundVo.getCallback())) {
            throw new PaymentException(ResultCodeEnum.CALLBACK_URL_UN_LEGAL);
        }
        if(orderRefundVo.getBusinessType() == null || orderRefundVo.getInvoker() == null || orderRefundVo.getPayChannel() == null) {
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR);
        }
        if (StringUtil.isEmpty(orderRefundVo.getTradeNo(), orderRefundVo.getBusinessType().getType(), orderRefundVo.getBusinessRefundNo(),
                orderRefundVo.getInvoker().getType(), orderRefundVo.getDescription())
                || Objects.isNull(orderRefundVo.getAmount())) {
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR);
        }

        if (orderRefundVo.getTradeNo().length() > Constants.MAX_STRING_LENGTH_NO ||
                orderRefundVo.getBusinessRefundNo().length() > Constants.MAX_STRING_LENGTH_NO ||
                orderRefundVo.getAmount() > Constants.MAX_AMOUNT || orderRefundVo.getAmount() < Constants.MIN_AMOUNT ||
                orderRefundVo.getDescription().length() > Constants.MAX_STRING_LENGTH_PRODUCT_NAME ) {
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR);
        }
    }

    /**
     * 退款参数校验
     * @param orderRefundVo
     * @throws PaymentException
     */
    public void checkOrderRefundVo(OrderRefundVoV1 orderRefundVo) throws PaymentException {
        if (orderRefundVo == null) {
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR);
        }
        if (StringUtils.isEmpty(orderRefundVo.getCallback())) {
            throw new PaymentException(ResultCodeEnum.CALLBACK_URL_UN_LEGAL);
        }
        if(orderRefundVo.getBusinessType() == null || orderRefundVo.getInvoker() == null || orderRefundVo.getPayChannel() == null) {
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR);
        }
        if (StringUtil.isEmpty(orderRefundVo.getTradeNo(), orderRefundVo.getBusinessType(), orderRefundVo.getBusinessRefundNo(),
                orderRefundVo.getInvoker(), orderRefundVo.getDescription())
                || Objects.isNull(orderRefundVo.getAmount())) {
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR);
        }

        if (orderRefundVo.getTradeNo().length() > Constants.MAX_STRING_LENGTH_NO ||
                orderRefundVo.getBusinessRefundNo().length() > Constants.MAX_STRING_LENGTH_NO ||
                orderRefundVo.getAmount() > Constants.MAX_AMOUNT || orderRefundVo.getAmount() < Constants.MIN_AMOUNT ||
                orderRefundVo.getDescription().length() > Constants.MAX_STRING_LENGTH_NO ) {
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR);
        }
        if(!businessTypeConfig.checkBusinessType(orderRefundVo.getBusinessType())) {
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR);
        }

    }

    public ResultVO<PayBo> pay(PayDto payDto) throws PaymentException {
        ResultVO<PayBo> resultVO = new ResultVO<>();

        //校验是否创建过订单
        PrePayOrder prePayOrder = prePayOrderService.getPrePayOrderByPayNo(payDto.getPayNo());
        if (prePayOrder == null) {
            LOGGER.warn("PaymentService appPay BusinessOrderNo error  BusinessOrderNo:{}", payDto.getPayNo());
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR);
        }
        //判断订单是否过期
        if (prePayOrder.getTimeExpire().getTime() <= System.currentTimeMillis()) {
            LOGGER.warn("PaymentService pay order is timeExpire payDto:{}", JSONUtils.toJSON(payDto));
            throw new PaymentException(ResultCodeEnum.ORDER_TIME_EXPIRE);
        }
        //校验是否已经支付过
        PayResultOrder payResultOrder = payResultOrderService.selectByPayNoAndChannel(payDto.getPayNo(), payDto.getChannel());
        if (payResultOrder != null) {
            LOGGER.warn("PaymentService appPay already pay payNo:{}, channel:{}", payDto.getPayNo(), payDto.getChannel());
            throw new PaymentException(ResultCodeEnum.ORDER_ALREADY_PAY);
        }
        payDto.setChannelPayNo(prePayOrder.getChannelPayNo());
        payDto.setCreateTime(prePayOrder.getCreateTime());
        payDto.setReceiverId(prePayOrder.getReceiverId());
        if(!StringUtils.isEmpty(prePayOrder.getExtra())) {
            Map<String, Object> map = new HashMap<>(8);
            if(payDto.getMap() != null) {
                map.putAll(payDto.getMap());
            }
            map.putAll(Objects.requireNonNull(JSONUtils.parse2Map(prePayOrder.getExtra(), String.class, Object.class)));
            payDto.setMap(map);
            payDto.setSubject((String) map.get("subject"));
            payDto.setBody((String) map.get("body"));
        }
        PayBo payBo = routeManager.pay(payDto, payDto.getChannel());
        resultVO.setResult(payBo);
        return resultVO;
    }


    public ResultVO<PayBo> channelDegradedPay(PayDto payDto) throws PaymentException {
        LOGGER.info("PaymentService#channelDegradedPay begin payDto:{}", JSON.toJSONString(payDto));
        //校验是否创建过订单
        PrePayOrder prePayOrder = prePayOrderService.getPrePayOrderByPayNo(payDto.getPayNo());

        if(prePayOrder == null) {
            LOGGER.warn("PaymentService appPay BusinessOrderNo error  BusinessOrderNo:{}", payDto.getPayNo());
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR);
        }

        //判断订单是否过期
        if(prePayOrder.getTimeExpire().getTime() <= System.currentTimeMillis()) {
            LOGGER.warn("PaymentService pay order is timeExpire payDto:{}", JSONUtils.toJSON(payDto));
            throw new PaymentException(ResultCodeEnum.ORDER_TIME_EXPIRE);
        }

        //校验是否已经支付过
        PayResultOrder payResultOrder = payResultOrderService.selectByPayNoAndChannel(payDto.getPayNo(), payDto.getChannel());

        if(payResultOrder != null) {
            LOGGER.warn("PaymentService appPay already pay payNo:{}, channel:{}", payDto.getPayNo(), payDto.getChannel());
            throw new PaymentException(ResultCodeEnum.ORDER_ALREADY_PAY);
        }

        payDto.setChannelPayNo(prePayOrder.getChannelPayNo());
        payDto.setCreateTime(prePayOrder.getCreateTime());
        payDto.setReceiverId(prePayOrder.getReceiverId());
        payDto.setBusinessType(prePayOrder.getBusinessType());
        payDto.setPayerId(prePayOrder.getPayer());

        if(!StringUtils.isEmpty(prePayOrder.getExtra())) {
            Map<String, Object> map = new HashMap<>(8);

            if(payDto.getMap() != null) {
                map.putAll(payDto.getMap());
            }

            map.putAll(Objects.requireNonNull(JSONUtils.parse2Map(prePayOrder.getExtra(), String.class, Object.class)));
            payDto.setMap(map);
            StringBuilder subject = new StringBuilder();
            subject.append((String) map.get("subject"));
            try {
                UserInfoVo userInfoVo = userInfoService2.queryUserInfo(payDto.getReceiverId());
                String name = userInfoVo.getBasicInfo().getName();
                name = StringUtils.remove(name, "(");
                name = StringUtils.remove(name, ")");
                name = StringUtils.remove(name, "（");
                name = StringUtils.remove(name, "）");
                subject.append("______");
                subject.append(name);

            }
            catch(Exception ex) {
                LOGGER.error(ex.getMessage(), ex);
            }
            payDto.setSubject(subject.toString());
            payDto.setBody((String) map.get("body"));
            payDto.setPayScene((String) map.get("payScene"));
        }

        return routeManager.channelDegradedPay(payDto, prePayOrder);
    }

    public ResultVO<AppPayBo> appPay(AppPayDto appPayDto) {
        ResultVO resultVO = new ResultVO();
        try{
            AppPayBo appPayBo;
            //校验是否创建过订单
            PrePayOrder prePayOrder = prePayOrderService.getPrePayOrderByPayNo(appPayDto.getPayNo());
            if (prePayOrder==null){
                LOGGER.warn("PaymentService appPay BusinessOrderNo error  BusinessOrderNo:{}", appPayDto.getBusinessOrderNo());
                return ResultVO.createParamError();
            }
            //校验是否已经支付过
            PayResultOrder payResultOrder = payResultOrderService.selectByPayNoAndChannel(prePayOrder.getPayNo(), appPayDto.getChannel());
            if (payResultOrder!=null){
                LOGGER.warn("PaymentService appPay already pay payNo:{}, channel:{}", prePayOrder.getPayNo(), appPayDto.getChannel());
                resultVO.setCode(ResultCodeEnum.ORDER_ALREADY_PAY.getCode());
                resultVO.setMsg(ResultCodeEnum.ORDER_ALREADY_PAY.getMsg());
                return resultVO;
            }
            //检查此订单是否是重复下单
            appPayBo = redisClient.get(appPayDto.getBusinessOrderType()+appPayDto.getPayNo()+channelModeMap.get(appPayDto.getChannel()+""+PayModeEnum.APP.getMode()), AppPayBo.class);
            if (appPayBo!=null){
                appPayBo.setPayNO(prePayOrder.getPayNo());
                resultVO.setResult(appPayBo);
                return resultVO;
            }
            appPayDto.setAmount(prePayOrder.getAmount());
            appPayDto.setPayNo(prePayOrder.getPayNo());
            appPayDto.setCallbackAddress(prePayOrder.getCallbackAddress());
            appPayDto.setBusinessType(prePayOrder.getBusinessType());
            appPayDto.setBusinessOrderType(prePayOrder.getBusinessOrderType());
            appPayDto.setReceiverId(prePayOrder.getReceiverId());
            appPayBo = routeManager.appPay(appPayDto, appPayDto.getChannel());
            appPayBo.setPayNO(prePayOrder.getPayNo());
            resultVO.setResult(appPayBo);
        }catch (PaymentException e){
            LOGGER.error("PaymentService appPay appPayDto:{}", JSON.toJSON(appPayDto), e);
            resultVO.setCode(e.getErrCode());
            resultVO.setMsg(e.getErrMsg());
            return resultVO;
        }
        return resultVO;
    }

    public ResultVO<MiniPayBo> miniPay(MiniPayDto miniPayDto) throws PaymentException {
        ResultVO resultVO = new ResultVO();
        MiniPayBo miniPayBo;
        try{
            PrePayOrder prePayOrder = prePayOrderService.getPrePayOrderByPayNo(miniPayDto.getPayNo());
            if (prePayOrder==null){
                LOGGER.warn("PaymentService appPay BusinessOrderNo error  BusinessOrderNo:{}", miniPayDto.getBusinessOrderNo());
                return ResultVO.createParamError();
            }
            //校验是否已经支付过
            PayResultOrder payResultOrder = payResultOrderService.selectByPayNoAndChannel(prePayOrder.getPayNo(), miniPayDto.getChannel());
            if (payResultOrder!=null){
                LOGGER.warn("PaymentService miniPay already pay payNo:{}, channel:{}", prePayOrder.getPayNo(), miniPayDto.getChannel());
                resultVO.setCode(ResultCodeEnum.ORDER_ALREADY_PAY.getCode());
                resultVO.setMsg(ResultCodeEnum.ORDER_ALREADY_PAY.getMsg());
                return resultVO;
            }
            //检查此订单是否是重复下单
            miniPayBo = redisClient.get(miniPayDto.getBusinessOrderType()+miniPayDto.getPayNo()+channelModeMap.get(miniPayDto.getChannel()+""+PayModeEnum.MINI.getMode()), MiniPayBo.class);
            if (miniPayBo!=null){
                miniPayBo.setPayNO(prePayOrder.getPayNo());
                resultVO.setResult(miniPayBo);
                return resultVO;
            }
            miniPayDto.setAmount(prePayOrder.getAmount());
            miniPayDto.setPayNo(prePayOrder.getPayNo());
            miniPayDto.setCallbackAddress(prePayOrder.getCallbackAddress());
            miniPayDto.setBusinessType(prePayOrder.getBusinessType());
            miniPayDto.setBusinessOrderType(prePayOrder.getBusinessOrderType());
            miniPayDto.setReceiverId(prePayOrder.getReceiverId());
            miniPayBo  = routeManager.miniPay(miniPayDto, miniPayDto.getChannel());
            miniPayBo.setPayNO(prePayOrder.getPayNo());
            resultVO.setResult(miniPayBo);
        }catch (PaymentException e){
            LOGGER.error("PaymentService miniPay miniPayDto:{}", JSON.toJSON(miniPayDto), e);
            resultVO.setCode(e.getErrCode());
            resultVO.setMsg(e.getErrMsg());
            return resultVO;
        }
        return resultVO;
    }


    /**
     * 更新退款单状态
     * @param refundNo
     * @param refundBO
     * @param refundOrder
     */
    public void updateRefundStatus(String refundNo, RefundBO refundBO, RefundOrder refundOrder) {
        // 更新状态
        int count = refundOrderMapper.updateStatusByRefundNo(refundNo, refundBO.getChannelRefundNo(), refundBO.getDate(), new Date(), refundBO.getStatus(), refundBO.getChannelTransactionNo(), refundOrder.getVersion());
        if (count < 1) {
            // 更新失败就检查一下订单状态然后处理
            RefundOrder rd = refundOrderMapper.selectByRefundNo(refundNo);
            if(RefundOrder.STATUS_FINISH.equals(rd.getStatus())) {
                // 订单状态已完成不需要其他的操作了
                return;
            }
            if(RefundOrder.STATUS_CREATE.equals(rd.getStatus())) {
                // 重新update一下
              count = refundOrderMapper.updateStatusByRefundNo(refundNo, refundBO.getChannelRefundNo(), refundBO.getDate(), new Date(), refundBO.getStatus(), refundBO.getChannelTransactionNo(), rd.getVersion());
              if(count == 1) {
                  return;
              }
            }
            LOGGER.error("PaymentService#record save failed refundBO:{}", JSON.toJSONString(refundBO));
            return;
        }
        LOGGER.info("PaymentService#record refundBO:{}", JSON.toJSONString(refundBO));
    }

    /**
     * 创建第三方退款单
     */
    private String createChannelRefundOrder(PayResultOrder payResultOrder, RefundDto refundDto, String refundNo, boolean resend) throws PaymentException {
        LOGGER.info("createChannelRefundOrder start {} {},resend:{}",refundDto.getBusinessRefundNo(),refundNo,resend);
        refundDto.setChannelChargeId(payResultOrder.getChannelChargeNo());
        refundDto.setBusinessOrderType(payResultOrder.getBusinessOrderType());
        refundDto.setTotalAmount(payResultOrder.getAmount());
        refundDto.setChannel(payResultOrder.getPayChannel());
        refundDto.setPayer(payResultOrder.getPayer());
        // 开始创建退款单
        refundDto.setRefundNo(refundNo);
        refundDto.setUserId(payResultOrder.getReceiveId());
        RefundOrder refundOrder;

        // 如果不是重发的 就存库
        if(!resend) {
            boolean needDelay = needDelayRefund(payResultOrder);
            LOGGER.info("needDelayRefund ReceiveId:{} result:{}",payResultOrder.getReceiveId(),needDelay);

            refundOrder = ConversionDomain.buildRefundDomain(refundDto, needDelay);
            refundOrderMapper.insert(refundOrder);

            // 需要延迟，直接返回
            if(needDelay) {
                return refundNo;
            }
        }
        else {
            refundOrder = refundOrderMapper.selectByRefundNo(refundNo);

            if(!RefundOrder.STATUS_DELAY.equals(refundOrder.getStatus())) {
                /**
                 * 重试前判断退款是否完成
                 */
                String transactionNo = refundOrder.getRefundNo();

                if(StringUtils.isNotBlank(refundOrder.getChannelRefundNo()) && refundOrder.getChannelRefundNo().startsWith(Constants.TOTAL_ORDER_NUMBER_PREFIX)) {
                    transactionNo = refundOrder.getChannelRefundNo().substring(NumberUtils.INTEGER_ONE);
                }

                QueryTransactionStatusDto queryTransactionStatusDto = QueryTransactionStatusDto.build(payResultOrder.getBusinessType(), payResultOrder.getBusinessOrderType(), transactionNo, QueryTransactionStatusDto.TRADE_TYPE_REFUND, refundOrder.getUserId(), refundOrder.getCreateTime(), refundOrder.getPayNo(),payResultOrder.getPayChannel());
                QueryResultBo queryResultBo = routeManager.queryTransactionStatus(queryTransactionStatusDto, payResultOrder.getPayChannel());

                if(queryResultBo != null && !QueryResultBo.FAILED.equals(queryResultBo.getStatus())) {
                    LOGGER.info("PaymentService#refund order already finished/processing, refundOrder={}, queryResultBo={}",
                            JSONUtils.toJSON(refundOrder), JSONUtils.toJSON(queryResultBo));
                    RefundOrder updObj = new RefundOrder();
                    updObj.setId(refundOrder.getId());
                    updObj.setStatus(RefundOrder.STATUS_IN);
                    updObj.setUpdateTime(DateUtil.now());
                    Date successTime = new Date();

                    if(queryResultBo.getSuccessTime() != null) {
                        if(payResultOrder.getPayChannel().contains(Constants.PINGAN)) {
                            successTime = DateUtil.unixToDate(queryResultBo.getSuccessTime() / 1000);
                        }
                        else {
                            successTime = DateUtil.unixToDate(queryResultBo.getSuccessTime());
                        }

                        updObj.setSuccessTime(successTime);
                    }

                    if(!StringUtils.isBlank(queryResultBo.getChannelChargeNo())) {
                        updObj.setChannelRefundNo(queryResultBo.getChannelChargeNo());
                    }

                    refundOrderMapper.updateByPrimaryKeySelective(updObj);

                    /**
                     * 成功的话给业务补一个MQ消息
                     */
                    if(QueryResultBo.FINISHED.equals(queryResultBo.getStatus())) {
                        mqProducer.syncSendDelayMsg(RocketMqTopic.TOPIC_SYNC_REFUND_CALLBACK, JSONUtils.toJSON(RefundDO.buildSuccessCallbackMsg(queryResultBo.getChannelChargeNo(), refundNo, successTime)), 3);
                    }

                    return refundOrder.getRefundNo();
                }

                if(RefundOrder.STATUS_FAIL.equals(refundOrder.getStatus())) {
                    // 平安渠道
                    if(refundDto.getChannel().startsWith(Constants.CHANNEL_PINGAN_NAME)) {
                        refundDto.setNeedChangeRefundNo(Boolean.TRUE);
                        refundDto.setChannelRefundNo(leafUtil.getSnowflakeId());
                    }
                    // 非平安渠道
                    else {
                        // 银联渠道，随机生成
                        if (refundDto.getChannel().contains(Constants.CHANNEL_UNIONPAY_NAME)) {
                            refundNo = leafUtil.getSnowflakeId();
                        } else {
                            // 退款流水号重新生成，原退款单号_01、02......
                            refundNo = ConcatStringUtil.genNo(refundOrder.getRefundNo());
                        }
                        refundDto.setRefundNo(refundNo);
                        RefundOrder updObj = new RefundOrder();
                        updObj.setId(refundOrder.getId());
                        updObj.setRefundNo(refundNo);
                        updObj.setUpdateTime(DateUtil.now());
                        refundOrderMapper.updateByPrimaryKeySelective(updObj);
                    }
                }
            }
        }

        refundDto.setTransDate(DateUtil.now());

        // 发送到消息队列，异步退款
        LOGGER.info("发送退款数据到rocketmq：{}", JSON.toJSONString(refundDto));
        mqProducer.syncSendDelayMsg(RocketMqTopic.TOPIC_RETRY_REFUND, JSON.toJSONString(refundDto), 0);

        return refundNo;
    }

    /**
     * 异步退款
     */
    public void refundAsync(RefundDto refundDto) {
        String lockKey = Constants.ORDER_REFUND_REDIS_KEY_PREFIX + "_retryRefund_" + refundDto.getRefundNo();
        if (!redisUtil.tryLock(lockKey)) {
            LOGGER.info("异步退款未获取到分布式锁，不进行处理 {}", JSON.toJSONString(refundDto));
            return;
        }
        try {
            // 查询支付结果信息
            PayResultOrder payResultOrder = payResultOrderService.selectByPayNoAndChannel(refundDto.getPayNo(), refundDto.getChannel());
            // 查询退款信息
            RefundOrder refundOrder = refundOrderMapper.selectByRefundNo(refundDto.getRefundNo());
            try {

                // 超过一年 线下退款
                Date paySuccessTime = payResultOrder.getCtime();
                if (DateUtil.isAtLeastOneYearApart(paySuccessTime)){
                    refundFailed(refundDto, refundOrder, payResultOrder, "超过一年的订单不支持在线退款，请联系平台财务线下给客户退款");
                    return;
                }

                // 京东银行卡超过半年线下退款
                if (ChannelEnum.JD_CARD.getCode().equals(refundDto.getChannel()) && DateUtil.isMoreThanSixMonthsAgo(paySuccessTime)) {
                    refundFailed(refundDto, refundOrder, payResultOrder, "京东银行卡超过半年的订单不支持在线退款，请联系平台财务线下给客户退款");
                    return;
                }

                // 验证退款状态
                if (!RefundOrder.STATUS_CREATE.equals(refundOrder.getStatus()) && !RefundOrder.STATUS_FAIL.equals(refundOrder.getStatus()) && !RefundOrder.STATUS_DELAY.equals(refundOrder.getStatus())) {
                    LOGGER.info("退款状态错误，不进行退款处理{}", JSON.toJSONString(refundOrder));
                    return;
                }

                // 不支持的渠道退款，提前拦截
                if (ChannelEnum.ABCHINA_LOAN.getCode().equals(refundDto.getChannel())) {
                    refundFailed(refundDto, refundOrder, payResultOrder, "农行链e贷暂不支持在线退款，请联系平台财务线下给客户退款");
                    return;
                }

                RefundBO refundBo = null;
                boolean isRetry = false;
                int maxRetryCount = 0;
                do {
                    try {
                        if (maxRetryCount > 0) {
                            Thread.sleep(2000);
                        }
                        // 发起退款
                        refundBo = routeManager.refund(refundDto);
                        isRetry = false;

                    } catch (PaymentException e) {
                        maxRetryCount ++;
                        isRetry = true;
                        LOGGER.error("refundAsync {}",refundDto.getRefundNo(),e);
                        if (maxRetryCount >= refundMaxRetryCount) {
                            throw e;
                        }
                    } catch (Exception e) {
                        LOGGER.error("refundAsync {}",refundDto.getRefundNo(),e);
                        throw e;
                    }

                    if (maxRetryCount > refundMaxRetryCount) {
                        isRetry = false;
                    }
                } while (isRetry);


                refundBo.setRefundNo(refundDto.getRefundNo());
                // 更新退款状态
                updateRefundStatus(refundDto.getRefundNo(), refundBo, refundOrder);
                // 回调业务
//                if (RefundOrder.STATUS_FINISH.equals(refundBo.getStatus())) {
//                    callbackRunner.callback(refundDto.getRefundNo(), CallbackResultVo.TYPE_REFUND_SUCCESS, refundOrder.getAmount(), System.currentTimeMillis() / 1000, refundOrder.getCallbackAddress(), payResultOrder.getMerchantId(), refundOrder.getBusinessRefundNo());
//                }

                if (Objects.equals(refundOrder.getChannel(), Constants.CHANNEL_XYD_LOAN) && RefundOrder.STATUS_FINISH.equals(refundBo.getStatus())) {
                    // 异步回调业务侧
                    Long unixTime = refundBo.getDate().getTime() / 1000;
                    callbackRunner.callback(refundOrder.getRefundNo(), CallbackResultVo.TYPE_REFUND_SUCCESS, refundOrder.getAmount(), unixTime, refundOrder.getCallbackAddress(), payResultOrder.getMerchantId(), refundOrder.getBusinessRefundNo(), refundBo.getRemark());
                }

                // 异步处理渠道充值撤销
                if ((refundOrder.getChannel().contains(Constants.CHANNEL_DIRECT_NAME)) && RefundOrder.STATUS_FINISH.equals(refundBo.getStatus())) {
                    RechargeHandleDto rechargeHandleDto = RechargeHandleDto.builder().
                            rechargeType(RechargeHandleDto.REFUND).
                            channel(refundOrder.getChannel()).
                            orderNo(refundOrder.getRefundNo()).
                            accountId(refundOrder.getUserId()).
                            build();
                    callbackRunner.syncSendMsg(RocketMqTopic.TOPIC_RECHARGE_HANDLE, rechargeHandleDto);
                }

            } catch (Exception e) {
                LOGGER.error("refundAsync:{}",refundDto.getRefundNo(),e);
                String failMsg = ResultCodeEnum.DEFAULT_ERROR.getCode() + ":" + ResultCodeEnum.DEFAULT_ERROR.getDisplayMsg();

                if (refundAlarmSwitch) {
                    DingTalkMessage talkMessage = DingTalkMessage.builder().type("退款").method("routeManager.refund").msg(e.getMessage()).param("refundNo:"+refundDto.getRefundNo()).build();
                    alarmService.alarm(talkMessage);
                }
                if (e instanceof PaymentException) {
                    PaymentException pe = (PaymentException) e;
                    failMsg = pe.getErrCode() + ":" + pe.getErrMsg();
                }

                // 退款失败的统一处理
                refundFailed(refundDto, refundOrder, payResultOrder, failMsg);
            }

        } finally {
            redisUtil.unLock(lockKey);
        }
    }

    private void refundFailed(RefundDto refundDto, RefundOrder refundOrder, PayResultOrder payResultOrder, String failMsg) {
        // 将订单改为失败
        RefundBO refundBo = new RefundBO();
        refundBo.setRefundNo(refundDto.getRefundNo());
        refundBo.setDate(new Date());
        refundBo.setStatus(RefundOrder.STATUS_FAIL);
        updateRefundStatus(refundDto.getRefundNo(), refundBo, refundOrder);

        // 异步回调业务
        callbackRunner.callback(refundDto.getRefundNo(), CallbackResultVo.TYPE_REFUND_FAIL, refundOrder.getAmount(), 0L, refundOrder.getCallbackAddress(), payResultOrder.getMerchantId(), refundOrder.getBusinessRefundNo(), failMsg);
    }

    /**
     * 平安渠道订单，平安未清分延迟退款
     */
    private boolean needDelayRefund(PayResultOrder payResultOrder) {
        if(!payResultOrder.getPayChannel().contains(Constants.CHANNEL_PINGAN_NAME) && PayResultOrder.ROYALTY_STATUS_NONE.equals(payResultOrder.getRoyalty())) {
            return false;
        }

        try {
            LOGGER.info("needDelayRefund payNo:{} ReceiveId:{} noDelaySwitch:{}",payResultOrder.getPayNo(),payResultOrder.getReceiveId(),noDelaySwitch);
            if (noDelaySwitch) {
                return false;
            }

            if (noDelayShopSwitch) {
                String userId = payAccountService.getBusinessUserIdByAccountId(payResultOrder.getReceiveId());
                LOGGER.info("needDelayRefund userId:{},shop:{} contains:{}",userId,POP_SHOP,POP_SHOP.contains(userId));

                if (StringUtils.isNotBlank(userId) && CollectionUtils.isNotEmpty(POP_SHOP) && POP_SHOP.contains(userId)) {
                    return false;
                }
            }

            // 1.当天支付当天退，不延迟
            String payDate = DateUtil.format(payResultOrder.getCtime(), DateUtil.DATEFORMATDAY_02);
            String refundDate = DateUtil.format(DateUtil.now(), DateUtil.DATEFORMATDAY_02);

            if(Objects.equals(payDate, refundDate)) {
                return false;
            }

            // 2.平安未清算，延迟
            QueryChannelClearVo queryChannelClearVo = routeManager.queryChannelClear(QueryChannelClearDto.builder().
                    businessIdType(payResultOrder.getBusinessOrderType()).
                    accountId(payResultOrder.getReceiveId()).
                    startDate(payDate).
                    endDate(payDate).
                    build());

            return !queryChannelClearVo.isClearFinish();
//            String nextDate = DateUtil.format(DateUtil.getNextDay(payResultOrder.getCtime()), DateUtil.DATEFORMATDAY);
//            Date startDateTime = DateUtil.parase(DateUtil.connect(nextDate, DateUtil.ZERO), DateUtil.DATEFORMATSECOND);
//            Date endDateTime = DateUtil.parase(DateUtil.connect(nextDate, DateUtil.NINE), DateUtil.DATEFORMATSECOND);
//            return DateUtil.isBetween(DateUtil.now(), startDateTime, endDateTime);
        }
        catch(Exception ex) {
            LOGGER.error(ex.getMessage(), ex);
            return false;
        }
    }

    /**
     * 创建退款单，先校验，再发起
     * @param payResultOrder
     * @param refundDto
     * @return
     * @throws PaymentException
     */
    public String createRefundOrder(PayResultOrder payResultOrder, RefundDto refundDto) throws PaymentException {
        // 同一笔定单 5 秒内不可以重复发起
        String redisKey = Constants.ORDER_REFUND_REDIS_KEY_PREFIX + refundDto.getPayNo();

        if (redisClient.exists(redisKey)) {
            throw new PaymentException(ResultCodeEnum.ORDER_CANNOT_REFUND_TEMPORARY);
        }

        redisClient.setEx(redisKey, refundDto.getPayNo(), refundLockExpireTime);
        PrePayOrder prePayOrder = prePayOrderMapper.getPrePayOrderByPayNo(payResultOrder.getPayNo());

        if(prePayOrder == null) {
            throw new PaymentException(ResultCodeEnum.ORDER_NOT_EXIST);
        }

        refundDto.setReceiverId(prePayOrder.getReceiverId());
        refundDto.setChannelPayNo(prePayOrder.getChannelPayNo());
        refundDto.setBusinessOrderNo(prePayOrder.getBusinessOrderNo());
        refundDto.setPayMode(payResultOrder.getPayMode());
        refundDto.setExtra(prePayOrder.getExtra());
        refundDto.setOrigChannelTransactionNo(payResultOrder.getChannelTransactionNo());
        //检查一下退款单有没有创建过
        RefundOrder refundOrder = refundOrderMapper.selectByBusinessRefundNo(payResultOrder.getPayNo(), refundDto.getBusinessRefundNo());

        if (payResultOrder.getPayChannel().contains(ChannelConstants.CHANNEL_PINGAN_NAME)) {
            refundDto.setOriTransDate(prePayOrder.getCreateTime());
        } else {
            refundDto.setOriTransDate(payResultOrder.getCtime());
        }


        StringBuilder description = new StringBuilder();

        try {
            UserInfoVo userInfoVo = userInfoService2.queryUserInfo(prePayOrder.getReceiverId());
            String name = userInfoVo.getBasicInfo().getName();
            name = StringUtils.remove(name, "(");
            name = StringUtils.remove(name, ")");
            name = StringUtils.remove(name, "（");
            name = StringUtils.remove(name, "）");
            description.append(name);
            description.append("_");
        }
        catch(Exception ex) {
            LOGGER.error(ex.getMessage(), ex);
        }

        description.append(prePayOrder.getBusinessOrderNo());
        refundDto.setDescription(description.toString());

        if(refundOrder != null) {
            if(RefundOrder.STATUS_CREATE.equals(refundOrder.getStatus()) ||
                    RefundOrder.STATUS_FAIL.equals(refundOrder.getStatus()) ||
                    RefundOrder.STATUS_DELAY.equals(refundOrder.getStatus()))
            {
                // 去第三方重新创建 退款单
                return  createChannelRefundOrder(payResultOrder, refundDto, refundOrder.getRefundNo(), true);
            }

            return refundOrder.getRefundNo();
        } else {
            LOGGER.info("{} refundOrder is not exist",refundDto.getBusinessRefundNo());
        }

        // 计算总共的退款金额
        int refundMoney = countRefundMoney(refundDto.getPayNo());

        // 检查金额
        if(payResultOrder.getAmount() < (refundMoney + refundDto.getAmount())) {
            throw new PaymentException(ResultCodeEnum.REFUND_MONEY_NOT_ENOUGH);
        }

        if(PayResultOrder.REFUND_STATUS_NONE.equals(payResultOrder.getRefund())) {
            // 改订单退款状态为有退款订单
            payResultOrderMapper.updateRefundStatusById(payResultOrder.getId(), new Date());
        }

        return createChannelRefundOrder(payResultOrder, refundDto, leafUtil.getSnowflakeId(), false);
    }

    /**
     * 计算累计退款金额
     * @param payNo
     */
    private int countRefundMoney(String payNo) {
        int refundMoney = 0;
        List<RefundOrder> refundOrders = refundOrderMapper.selectListByPayNo(payNo);

        if(refundOrders != null && !refundOrders.isEmpty()) {
            for(RefundOrder rd : refundOrders) {
                // 如果退款单状态不是退款失败
                if (!rd.getStatus().equals(RefundOrder.STATUS_FAIL)) {
                    refundMoney += rd.getAmount();
                }
            }
        }

        return refundMoney;
    }

    /**
     * 提现
     */
    public OrderWithdrawalBo withdraw(WithdrawalDto withdrawalDto) throws PaymentException {
        LOGGER.info("PaymentService#withdraw withdrawalDto:{}", JSON.toJSONString(withdrawalDto));

        // 幂等
        WithdrawOrder withdraw = withdrawOrderMapper.selectByBusinessWithdrawNo(withdrawalDto.getBusinessType(), withdrawalDto.getBusinessWithdrawNo());

        if(null != withdraw) {
            if(WithdrawOrder.STATUS_CREATE.equals(withdraw.getStatus())) {
                throw new PaymentException(ResultCodeEnum.WITHDRAW_ORDER_CREAT_REPEAT);
            }

            if(WithdrawOrder.STATUS_IN.equals(withdraw.getStatus())) {
                throw new PaymentException(ResultCodeEnum.WITHDRAW_IN);
            }

            if(WithdrawOrder.STATUS_FINISH.equals(withdraw.getStatus())) {
                throw new PaymentException(ResultCodeEnum.WITHDRAW_SUCCEED);
            }
        }

        /**
         * 详见方案#提现流程 https://wiki.int.ybm100.com/pages/viewpage.action?pageId=422666394
         * 1.结算中不允许提现
         * 2.提现前先检查是否有待冻结账单
         */
        if(redisUtil.exists(RedisUtil.D_AMOUNT_LOCK + withdrawalDto.getUserId())) {
            throw new PaymentException(ResultCodeEnum.PROFIT_SHARING_WITHDRAWAL_IS_NOT_ALLOWED);
        }

        // 冻结账单
        frozenSettlementService.checkWithdrawal(withdrawalDto.getBusinessIdType(), withdrawalDto.getBusinessId());

        initWithdrawalDto(withdrawalDto);

        QueryAccountDto queryAccountDto = new QueryAccountDto();
        queryAccountDto.setUserId(withdrawalDto.getUserId());
        queryAccountDto.setBusinessIdType(withdrawalDto.getBusinessIdType());
        queryAccountDto.setThirdUserId(withdrawalDto.getChannelUserId());
        UserBo userBo = routeManager.queryAccount(queryAccountDto);

        if(userBo.getWithdrawableBalance() < withdrawalDto.getAmount()) {
            LOGGER.warn("PaymentService#withdraw balance not enough SeparateAccountsVo:{}", JSON.toJSONString(withdrawalDto));
            throw new PaymentException(ResultCodeEnum.USER_BALANCE_NOT_ENOUGH);
        }

        QueryWithdrawalFeeBo queryWithdrawalFeeBo = getFeeByWithdrawalAmount(withdrawalDto.getBusinessType(), withdrawalDto.getUserId(), withdrawalDto.getAmount());
        withdrawalDto.setFee(queryWithdrawalFeeBo.getFee());

        // 插入数据
        WithdrawOrder withdrawOrder = ConversionDomain.buildWithdrawDomain(withdrawalDto);

        if(null != withdraw) {
            withdrawOrder.setId(withdraw.getId());
            withdrawOrder.setCreateTime(null);
            withdrawOrderMapper.updateByPrimaryKeySelective(withdrawOrder);
        }
        else {
            withdrawOrderMapper.insert(withdrawOrder);
        }

        WithdrawOrder updateWithdrawOrder = new WithdrawOrder();
        updateWithdrawOrder.setId(withdrawOrder.getId());
        PaymentException exception = null;
        OrderWithdrawalBo result = new OrderWithdrawalBo();

        // 发起提现&更新提现信息
        try {
            WithdrawalBO withdrawalBO = routeManager.withdraw(withdrawalDto, withdrawalDto.getAccountChannel());
            updateWithdrawOrder.setBusinessUserName(withdrawalBO.getUserName());
            updateWithdrawOrder.setChannelWithdrawNo(withdrawalBO.getChannelWithdrawNo());
            result.setNeedAuth(withdrawalBO.getNeedAuth());

            if(withdrawalBO.needNotice()) {
                mqProducer.syncSendDelayMsg(RocketMqTopic.TOPIC_WITHDRAW_NOTICE,
                        JSONUtils.toJSON(withdrawalBO.noticeParam()), MqConstant.DELAY_LEVELS[NumberUtils.INTEGER_ONE]);
            }
        }
        catch(Exception ex) {
            LOGGER.error("PaymentService#withdraw error, message=" + ex.getMessage() ,ex);
            updateWithdrawOrder.setStatus(WithdrawOrder.STATUS_FAIL);

            if(ex instanceof PaymentException) {
                exception = (PaymentException) ex;
            }
            else {
                exception = new PaymentException(ResultCodeEnum.FBANK_SYSTEM_ERROR);
            }
        }

        updateWithdrawOrder.setUpdateTime(DateUtil.now());
        withdrawOrderMapper.updateByPrimaryKeySelective(updateWithdrawOrder);

        // 异常不为空，抛出异常
        if(exception != null) {
            throw exception;
        }

        result.setWithdrawalNo(withdrawOrder.getWithdrawNo());
        LOGGER.info("PaymentService#withdraw end. orderWithdrawalBo:{}", JSONUtils.toJSON(result));
        return result;
    }

    /**
     * 提现鉴权
     */
    public OrderWithdrawalBo withdrawalAuth(WithdrawAuthVo withdrawAuthVo) throws PaymentException {
        LOGGER.info("PaymentService#withdrawalAuth withdrawAuthVo:{}", JSON.toJSONString(withdrawAuthVo));
        WithdrawOrder withdrawOrder = StringUtils.isNotBlank(withdrawAuthVo.getWithdrawalNo()) ?
                withdrawOrderMapper.selectByWithdrawNo(withdrawAuthVo.getWithdrawalNo()) :
                withdrawOrderMapper.selectByBusinessWithdrawNo(withdrawAuthVo.getBusinessIdType(), withdrawAuthVo.getBusinessWithdrawNo());

        if(withdrawOrder == null) {
            throw new PaymentException(ResultCodeEnum.TRADE_NOT_EXIT_ERROR);
        }

        if(!WithdrawOrder.STATUS_IN.equals(withdrawOrder.getStatus())) {
            throw new PaymentException(ResultCodeEnum.ORDER_STATUS_NOT_MATCH);
        }

        WithdrawAuthDto withdrawAuthDto = WithdrawAuthDto.builder().
                businessIdType(withdrawAuthVo.getBusinessIdType()).
                withdrawalNo(withdrawOrder.getWithdrawNo()).
                channelWithdrawNo(withdrawOrder.getChannelWithdrawNo()).
                activeCode(withdrawAuthVo.getActiveCode()).
                accountId(withdrawOrder.getUserId()).
                build();
        WithdrawalBO withdrawalBO = routeManager.withdrawAuth(withdrawAuthDto);
        LOGGER.info("PaymentService#withdrawalAuth withdrawalBO:{}", JSON.toJSONString(withdrawalBO));
        return OrderWithdrawalBo.build(withdrawOrder.getWithdrawNo());
    }

    /**
     *
     * @param queryWithdrawalFeeVo
     * @throws PaymentException
     */
    public QueryWithdrawalFeeBo withdrawalFee(QueryWithdrawalFeeVo queryWithdrawalFeeVo) throws PaymentException {
        String userId = payAccountService.getAccountIdByBusiness(queryWithdrawalFeeVo.getBusinessIdType(), queryWithdrawalFeeVo.getBusinessUserId());
        ThirdUserInfo thirdUserInfo = thirdUserInfoService.getThirdUserInfoByUserId(userId);
        if(thirdUserInfo == null || thirdUserInfo.getThirdUserId().isEmpty()) {
            throw new PaymentException(ResultCodeEnum.GET_USER_STATE_ERROR);
        }
        return getFeeByWithdrawalAmount(queryWithdrawalFeeVo.getBusinessType(), userId, queryWithdrawalFeeVo.getAmount());
    }

    public QueryWithdrawalFeeBo getFeeByWithdrawalAmount(String businessType, String userId, int amount) throws PaymentException {
        ThirdUserInfo thirdUserInfo = thirdUserInfoService.getThirdUserInfoByUserId(userId);
        if(thirdUserInfo == null || thirdUserInfo.getThirdUserId().isEmpty()) {
            throw new PaymentException(ResultCodeEnum.THIRD_USER_ID_NOT_EXIST);
        }
        QueryWithdrawalFeeBo queryWithdrawalFeeBo = new QueryWithdrawalFeeBo();
        if(BusinessTypeEnum.EC.getType().equals(businessType)) {
            queryWithdrawalFeeBo.setFee(0);
            queryWithdrawalFeeBo.setExtraMsg("药帮忙手续费平台承担 0元/笔");
            return queryWithdrawalFeeBo;
        }
        if(BusinessTypeEnum.YIKUAIQIAN.getType().equals(businessType) && Constants.CHANNEL_YOP_NAME.equals(thirdUserInfo.getThirdType())) {
            queryWithdrawalFeeBo.setFee(0);
            queryWithdrawalFeeBo.setExtraMsg("荷叶易宝渠道平台承担 0元/笔");
            return queryWithdrawalFeeBo;
        }
        if(Constants.FUMIN_BANK_NAME.equals(thirdUserInfo.getSettleBank())) {
            queryWithdrawalFeeBo.setFee(0);
            queryWithdrawalFeeBo.setExtraMsg("富民银行卡提现 0元/笔");
            return queryWithdrawalFeeBo;
        }
        if(Constants.PERSONAL.equals(thirdUserInfo.getUserType())) {
            queryWithdrawalFeeBo.setFee(20);
            queryWithdrawalFeeBo.setExtraMsg("个人银行卡提现 0.2元/笔");
            return queryWithdrawalFeeBo;
        }
        if(Constants.MERCHANT.equals(thirdUserInfo.getUserType())) {
            //5万及以下 5毛 （含）
            if(amount <= Constants.WITHDRAWAL_AMOUNT_5W) {
                queryWithdrawalFeeBo.setFee(50);
                queryWithdrawalFeeBo.setExtraMsg(Constants.withdrawalExtraMsg);
                return queryWithdrawalFeeBo;
            }
            //20万以下 （不含）
            if(amount < Constants.WITHDRAWAL_AMOUNT_20W) {
                queryWithdrawalFeeBo.setFee(500);
                queryWithdrawalFeeBo.setExtraMsg(Constants.withdrawalExtraMsg);
                return queryWithdrawalFeeBo;
            } else {
                queryWithdrawalFeeBo.setFee(600);
                queryWithdrawalFeeBo.setExtraMsg(Constants.withdrawalExtraMsg);
                return queryWithdrawalFeeBo;
            }
        }
        throw new PaymentException(ResultCodeEnum.DEFAULT_ERROR);
    }

    private void initWithdrawalDto(WithdrawalDto withdrawalDto) throws PaymentException {
        String userId = payAccountService.getAccountIdByBusiness(withdrawalDto.getBusinessIdType(), withdrawalDto.getBusinessId());

        if(StringUtils.isBlank(userId)) {
            throw new PaymentException(ResultCodeEnum.USER_NOT_FOUND);
        }

        withdrawalDto.setWithdrawalNo(leafUtil.getSnowflakeId());
        withdrawalDto.setUserId(userId);
        ThirdUserInfo thirdUserInfo = thirdUserInfoMapper.getThirdUserInfoByUserId(userId);

        if(thirdUserInfo == null) {
            throw new PaymentException(ResultCodeEnum.THIRD_USER_ID_NOT_EXIST);
        }

        withdrawalDto.setSettleAccount(thirdUserInfo.getSettleAccount());
        withdrawalDto.setChannelUserId(thirdUserInfo.getThirdUserId());
        withdrawalDto.setChannelType(thirdUserInfo.getThirdType());
    }

    public void checkOrderWithdrawVo(OrderWithdrawVo orderWithdrawVo) throws PaymentException {
        if (orderWithdrawVo == null|| orderWithdrawVo.getBusinessType() == null || orderWithdrawVo.getAccountChannelEnum() == null) {
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR);
        }
        if (StringUtils.isEmpty(orderWithdrawVo.getCallback())) {
            throw new PaymentException(ResultCodeEnum.CALLBACK_URL_UN_LEGAL);
        }
        if (StringUtil.isEmpty(orderWithdrawVo.getBusinessId())
                || Objects.isNull(orderWithdrawVo.getAmount())) {
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR);
        }
        if (orderWithdrawVo.getAmount() < Constants.MIN_AMOUNT ||
                orderWithdrawVo.getAmount() > Constants.MAX_AMOUNT ||
                orderWithdrawVo.getBusinessId().length() > Constants.MAX_STRING_LENGTH_DESC) {
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR);
        }
        if(!businessTypeConfig.checkBusinessType(orderWithdrawVo.getBusinessType())) {
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR);
        }
    }

    public boolean checkCompliance(String payerId, String payerName, String sellerName) throws PaymentException {
        if (StringUtil.isEmpty(payerId)||StringUtil.isEmpty(payerName)||StringUtil.isEmpty(sellerName)){
            return false;
        }
        return true;
    }

    public void checkOrderClosedVo(OrderClosedVo orderClosedVo) throws PaymentException {
        if (null == orderClosedVo) {
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR);
        }
        if (orderClosedVo.getInvoker()==null||orderClosedVo.getBusinessOrderType()==null||orderClosedVo.getBusinessType()==null){
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR);
        }
        if (orderClosedVo.getChannel()==null){
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR);
        }
    }

    public OrderClosedBo orderClosed(OrderClosedDto orderClosedDto) throws PaymentException {
        OrderClosedBo orderClosedBo = new OrderClosedBo();
        String redisKey = Constants.ORDER_CLOSED_REDIS_KEY_PREFIX + orderClosedDto.getPayNo();
        try {
            if(redisClient.exists(redisKey)){
                throw new PaymentException(ResultCodeEnum.ORDER_ALREADY_CLOSED_PROCCESSING);
            }else {
                redisClient.setEx(redisKey, orderClosedDto.getPayNo(), 5);
            }
            PrePayOrder order = orderService.getPrePayOrderByPayNo(orderClosedDto.getPayNo());
            orderClosedBo.setPayNo(orderClosedDto.getPayNo());
            if (order == null) {
                throw new PaymentException(ResultCodeEnum.TRADE_NOT_EXIT_ERROR);
            }
            if (order.getStatus()==2) {
                throw new PaymentException(ResultCodeEnum.ORDER_ALREADY_CLOSED);
            }
            List<PayResultOrder> payResultOrders = orderService.selectPayResultOrderListByPayNo(orderClosedDto.getPayNo());
            if (CollectionUtils.isNotEmpty(payResultOrders)) {
                throw new PaymentException(ResultCodeEnum.ORDER_ALREADY_PAY_NO_CLOSED);
            }
            orderClosedDto.setPayer(order.getPayer());
            orderClosedDto.setOriTransDate(order.getCreateTime());
            orderClosedDto.setTransDate(new Date());
            orderClosedDto.setChannelChargeId(order.getChannelPayNo());
            orderClosedDto.setPrePayOrderId(order.getId());
            //更新状态
            PrePayOrder prePayOrder = new PrePayOrder();
            prePayOrder.setStatus((byte)2);
            prePayOrder.setId(order.getId());
            orderClosedBo = routeManager.orderClosed(orderClosedDto, orderClosedDto.getChannel());
            orderService.updatePrePayOrderById(prePayOrder);
            orderClosedBo.setStatus(OrderClosedBo.SUCCESS);
        } catch (PaymentException e){
            throw new PaymentException(e.getErrCode(),e.getErrMsg());
        } catch (Exception e) {
            LOGGER.info("orderClosed 异常，异常信息：",e);
            throw new PaymentException(ResultCodeEnum.ORDER_CLOSED_ERROR);
        }
        return orderClosedBo;
    }

    /**
     * 账户转账
     */
    public SeparateAccountsBo separateAccountsFromUser(SeparateAccountFromUserVo separateAccountFromUserVo) throws PaymentException {
        SeparateAccountsBo separateAccountsBo = new SeparateAccountsBo();
        SeparateAccountFromUserDto separateAccountFromUserDto = new SeparateAccountFromUserDto();
        List<SeparateOrder> separateOrders = separateOrderService.querySeparateOrderByBusinessOrderNo(separateAccountFromUserVo.getBusinessOrderNo());

        // 此处需要幂等
        if(separateOrders != null && separateOrders.size() > 0) {
            if(separateOrders.size() > 1) {
                // 以前用过单号
                throw new PaymentException(ResultCodeEnum.PARAM_ERROR, "订单号重复");
            }

            if(separateOrders.get(0).getStatus()!=null && SeparateOrder.STATUS_FINISH.equals(separateOrders.get(0).getStatus())) {
                separateAccountsBo.setUserSeparateNo(separateOrders.get(0).getSeparateNo());
                return separateAccountsBo;
            }
            else {
                String separateNo = separateOrders.get(0).getSeparateNo();
                String userIdOut = payAccountService.getAccountIdByBusiness(StringUtils.isNotBlank(separateAccountFromUserVo.getBusinessIdTypeOut()) ?
                        separateAccountFromUserVo.getBusinessIdTypeOut() : separateAccountFromUserVo.getBusinessIdType(), separateAccountFromUserVo.getBusinessUserIdOut());
                QueryTransactionStatusDto queryTransactionStatusDto = QueryTransactionStatusDto.build(separateAccountFromUserVo.getBusinessOrderType(),
                        separateNo, QueryTransactionStatusDto.TRADE_TYPE_TRANSFER, userIdOut, separateOrders.get(0).getCtime());
                QueryResultBo queryResultBo = routeManager.queryTransactionStatus(queryTransactionStatusDto, StringUtils.EMPTY);

                if(queryResultBo != null && QueryResultBo.FINISHED.equals(queryResultBo.getStatus())) {
                    LOGGER.info("PaymentService#separateAccountsFromUser is finished, separateNo {}", separateNo);
                    separateOrderService.updateSeparateOrderStatusBySeparateNo(separateNo, SeparateOrder.STATUS_FINISH);
                    separateAccountsBo.setUserSeparateNo(separateNo);
                    return separateAccountsBo;
                }

                // 原单号
                separateAccountFromUserDto.setSeparateNo(separateNo);
            }
        }
        else {
            separateAccountFromUserDto.setSeparateNo(leafUtil.getSnowflakeId());
        }

        separateAccountFromUserDto.setAmount(separateAccountFromUserVo.getAmount());
        separateAccountFromUserDto.setBusinessIdType(separateAccountFromUserVo.getBusinessIdType());
        separateAccountFromUserDto.setBusinessOrderNo(separateAccountFromUserVo.getBusinessOrderNo());
        separateAccountFromUserDto.setBusinessOrderType(separateAccountFromUserVo.getBusinessOrderType());
        separateAccountFromUserDto.setBusinessUserIdIn(separateAccountFromUserVo.getBusinessUserIdIn());
        separateAccountFromUserDto.setBusinessUserIdOut(separateAccountFromUserVo.getBusinessUserIdOut());
        separateAccountFromUserDto.setBusinessIdTypeIn(separateAccountFromUserVo.getBusinessIdTypeIn());
        separateAccountFromUserDto.setBusinessIdTypeOut(separateAccountFromUserVo.getBusinessIdTypeOut());
        separateAccountFromUserDto.setAmount(separateAccountFromUserVo.getAmount());
        separateAccountFromUserDto.setExtra(separateAccountFromUserVo.getExtra());

        String businessIdTypeOut = StringUtils.isNotBlank(separateAccountFromUserVo.getBusinessIdTypeOut()) ?
                separateAccountFromUserVo.getBusinessIdTypeOut() : separateAccountFromUserVo.getBusinessIdType();
        UserBo userBo = accountService.queryAccountByBusinessId(separateAccountFromUserVo.getBusinessUserIdOut(), businessIdTypeOut, AccountChannelEnum.AGGREGATE.getChannel());

        if(userBo.getWithdrawableBalance() < separateAccountFromUserDto.getAmount()) {
            LOGGER.warn("PaymentService#separateAccountsFromPlatform balance not enough SeparateAccountsVo:{}", JSON.toJSONString(separateAccountFromUserDto));
            throw new PaymentException(ResultCodeEnum.USER_BALANCE_NOT_ENOUGH);
        }

        String userIdIn = payAccountService.getAccountIdByBusiness(StringUtils.isNotBlank(separateAccountFromUserDto.getBusinessIdTypeIn()) ?
                separateAccountFromUserDto.getBusinessIdTypeIn() : separateAccountFromUserDto.getBusinessIdType(), separateAccountFromUserDto.getBusinessUserIdIn());
        String userIdOut = payAccountService.getAccountIdByBusiness(StringUtils.isNotBlank(separateAccountFromUserDto.getBusinessIdTypeOut()) ?
                separateAccountFromUserDto.getBusinessIdTypeOut() : separateAccountFromUserDto.getBusinessIdType(), separateAccountFromUserDto.getBusinessUserIdOut());
        SeparateAccountDO separateAccountDO = buildUserSeparateDO(userIdIn, userIdOut, separateAccountFromUserDto);
        saveSeparateAccounts(separateAccountDO);
        SeparateAccountsBo result = routeManager.separateAccountBetweenUser(separateAccountDO);
        separateOrderService.updateSeparateOrderStatusBySeparateNo(separateAccountDO.getSeparateNo(), SeparateOrder.STATUS_FINISH);
        return result;
    }

    /**
     * 存流水
     * @param separateAccountDO
     */
    private void saveSeparateAccounts(SeparateAccountDO separateAccountDO) {
        SeparateOrder separateOrder = separateOrderService.querySeparateOrderBySeparateNo(separateAccountDO.getSeparateNo());

        if(separateOrder != null) {
            return;
        }

        separateOrder = new SeparateOrder();
        separateOrder.setSeparateNo(separateAccountDO.getSeparateNo());
        separateOrder.setTradeAmount(separateAccountDO.getTradeAmount());
        separateOrder.setSettleNo(separateAccountDO.getSettleId());
        separateOrder.setCustMerchant(separateAccountDO.getCustMerchantNo());
        separateOrder.setCustNoIn(separateAccountDO.getCustNoIn());
        separateOrder.setBusinessOrderNo(separateAccountDO.getBusinessOrderNo());
        separateOrder.setUtime(new Date());
        separateOrder.setCtime(new Date());
        separateOrder.setStatus(SeparateOrder.STATUS_CREATE);
        separateOrder.setExtra(separateAccountDO.getTradeRemark());
        SeparateOrder result = separateOrderService.insert(separateOrder);

        if(result == null) {
            LOGGER.error("PaymentService#saveSeparateAccounts error separateOrder:{}", JSON.toJSONString(separateOrder));
        }
    }

    private SeparateAccountDO buildUserSeparateDO(String acceptUserId, String fromUserId, SeparateAccountFromUserDto separateAccountFromUserDto) throws PaymentException {
        UserInfo acceptUserInfo = userInfoService.queryUserInfoByAccountId(acceptUserId);
        ThirdUserInfo acceptThirdUserInfo = thirdUserInfoService.getThirdUserInfoByUserId(acceptUserId);
        //取出结算账户信息
        SettleAccountDto acceptSettleAccountDto = decryptUserSettleAccount(acceptUserInfo);
        UserInfo fromUserInfo = userInfoService.queryUserInfoByAccountId(fromUserId);
        SettleAccountDto fromUserSettleAccount = decryptUserSettleAccount(fromUserInfo);
        ThirdUserInfo fromThirdUserInfo = thirdUserInfoService.getThirdUserInfoByUserId(fromUserId);
        SeparateAccountDO separateAccountDO = new SeparateAccountDO();
        separateAccountDO.setSeparateNo(separateAccountFromUserDto.getSeparateNo());
        separateAccountDO.setTradeAmount(separateAccountFromUserDto.getAmount());
        separateAccountDO.setCustMerchantNo(fromThirdUserInfo.getUserId());
        separateAccountDO.setNameOut(fromUserSettleAccount.getRecipient().getName());
        separateAccountDO.setAccountOut(fromThirdUserInfo.getThirdUserId());
        separateAccountDO.setAccountIn(acceptThirdUserInfo.getThirdUserId());
        separateAccountDO.setNameIn(acceptSettleAccountDto.getRecipient().getName());
        separateAccountDO.setCustNoIn(acceptThirdUserInfo.getUserId());
        separateAccountDO.setSettleId("");
        separateAccountDO.setBusinessOrderNo(separateAccountFromUserDto.getBusinessOrderNo());
        separateAccountDO.setBusinessOrderType(separateAccountFromUserDto.getBusinessOrderType());
        separateAccountDO.setTradeRemark(separateAccountFromUserDto.getExtra());
        return separateAccountDO;
    }

    private SettleAccountDto decryptUserSettleAccount(UserInfo userInfo) throws PaymentException {
        SettleAccountDto acceptSettleAccountDto = null;

        try {
            String jsonSettleAccount = AESUtil.decrypt(userInfo.getSettleInfo(), ApolloUtil.getProperty(Constants.USER_INFO_KEY));
            acceptSettleAccountDto = JSONUtils.parse2Object(jsonSettleAccount, SettleAccountDto.class);
            LOGGER.info("PaymentService#decryptUserSettleAccount SettleInfo update before:{}", jsonSettleAccount);
            String jsonBaseInfo = AESUtil.decrypt(userInfo.getBasicInfo(), ApolloUtil.getProperty(Constants.USER_INFO_KEY));
            UserDto baseInfoDto = JSONUtils.parse2Object(jsonBaseInfo, UserDto.class);
            LOGGER.info("PaymentService#decryptUserSettleAccount BasicInfo:{}", jsonBaseInfo);

            //修复结算账户名和基本信息中的用户名不一致的问题
            if(acceptSettleAccountDto != null && baseInfoDto != null) {
                Recipient recipient = acceptSettleAccountDto.getRecipient();
                recipient.setName(baseInfoDto.getName());
                acceptSettleAccountDto.setRecipient(recipient);
                LOGGER.info("PaymentService#decryptUserSettleAccount SettleInfo update after:{}", JSONUtils.toJSON(acceptSettleAccountDto));
            }
        }
        catch(Exception e) {
            LOGGER.error("PaymentService#buildSeparateAccountDO acceptUserInfo decrypt error userInfo:{}", JSONUtils.toJSON(userInfo));
        }

        if(acceptSettleAccountDto == null) {
            throw new PaymentException(ResultCodeEnum.THIRD_USER_ID_NOT_EXIST, "用户信息取出失败");
        }

        return acceptSettleAccountDto;
    }

    public void initJdCreditRepayPlan(Long minId, Long maxId) {
        LOGGER.info("initJdCreditRepayPlan task start {} {}", minId, maxId);
        Runnable task = () -> {
            Long startId = minId == null ? 0: minId, endId = maxId;
            while (true) {
                try {
                    LOGGER.info("initJdCreditRepayPlan task startId:{}", startId);
                    List<PayResultOrder> resultOrders = payResultOrderMapper.queryByIdLimit(ChannelEnum.JD_CREDIT.getCode(), startId, endId, 50);
                    if (resultOrders == null || resultOrders.isEmpty()) {
                        break;
                    }

                    //查询订单信息
                    List<String> payNos = resultOrders.stream().map(r -> r.getPayNo()).collect(Collectors.toList());
                    List<OrderPayBusinessDto> payBusinessDtoList = orderPayBusinessApi.queryOrderByTargetOrderIds(payNos);

                    // 查退款单信息-退款成功的
                    List<String> orderNos = payBusinessDtoList.stream().map(p -> p.getOrderNo()).collect(Collectors.toList());
                    List<OrderRefundBusinessDto> refundBusinessDtoList = orderPayBusinessApi.queryRefundSuccessByOrderNos(orderNos);
                    Map<String, List<OrderRefundBusinessDto>> refundDtoMap = refundBusinessDtoList.stream().collect(Collectors.groupingBy(r -> r.getOrderNo()));


                    // 构造数据
                    Map<String, PayResultOrder> resultOrderMap = resultOrders.stream().collect(Collectors.toMap(r -> r.getPayNo(), r -> r));
                    List<OrderTransDataPo> transDataPos = PayCommonConverter.buildTransDataPos(payBusinessDtoList, resultOrderMap, refundDtoMap);
                    Tuple2<List<LoanRepayPlanPo>, List<LoanRepayPlanDetailPo>> tuple2 = fetchRepayPlans(resultOrderMap, payBusinessDtoList);


                    // 持久化数据
                    orderService.batchInsertRepayPlans(transDataPos, tuple2.getT1(), tuple2.getT2());

                    // 更新下一轮数据
                    startId = resultOrders.stream().mapToLong(r -> r.getId()).max().getAsLong();
                } catch (Exception e) {
                    LOGGER.error("initJdCreditRepayPlan#error startId:{}", startId, e);
                    break;
                }
            }
        };
        new Thread(task, "initJdCreditRepayPlan").start();
    }

    private Tuple2<List<LoanRepayPlanPo>, List<LoanRepayPlanDetailPo>> fetchRepayPlans(Map<String, PayResultOrder> resultOrderMap, List<OrderPayBusinessDto> payDtoList) throws Exception {
        if (payDtoList == null || payDtoList.isEmpty()) {
            return new Tuple2<>(Collections.emptyList(), Collections.emptyList());
        }


        // 获取数据
        List<LoanInfoResponse> responses = new ArrayList<>();
        for (OrderPayBusinessDto payBusinessDto : payDtoList) {
            PayResultOrder resultOrder = resultOrderMap.get(payBusinessDto.getTargetOrderId());
            if (resultOrder == null) {
                LOGGER.error("fetchRepayPlans not match resultOrder {}", payBusinessDto.getTargetOrderId());
                continue;
            }

            try {
                LoanInfoResponse response = jdCreditAdapter.queryLoanDetail(null, resultOrder.getPayNo());
                responses.add(response);
            } catch (Exception e) {
                LOGGER.error("jdCreditAdapter.queryLoanDetail error:{}", resultOrder.getPayNo(), e);
            }
        }


        // 组装数据
        Map<String, String> payNoOrderMap = payDtoList.stream().collect(Collectors.toMap(p -> p.getTargetOrderId(), p -> p.getOrderNo(), (o, n) -> o));

        List<LoanRepayPlanPo> repayPlanPos = new ArrayList<>(responses.size());
        List<LoanRepayPlanDetailPo> repayPlanDetailPos = new ArrayList<>();
        for (LoanInfoResponse response : responses) {
            String businessOrderNo = payNoOrderMap.get(response.getOrderCode());
            Tuple2<LoanRepayPlanPo, List<LoanRepayPlanDetailPo>> tuple2 = PayCommonConverter.convertToRepayPo(response, businessOrderNo);
            if (tuple2.getT1() != null) {
                repayPlanPos.add(tuple2.getT1());
            }
            repayPlanDetailPos.addAll(tuple2.getT2());

        }
        return new Tuple2<>(repayPlanPos, repayPlanDetailPos);
    }
}
